package br.edu.ifrn.potigol.lang;

import java.util.HashMap;

import br.edu.ifrn.potigol.lang.PotigolClass;
import br.edu.ifrn.potigol.lang.PotigolException;
import br.edu.ifrn.potigol.lang.PotigolObject;
import br.edu.ifrn.potigol.lang.PotigolRuntime;
import br.edu.ifrn.potigol.lang.TypeError;
import br.edu.ifrn.potigol.lang.ValueObject;


/**
 * Qualquer objeto, instância de classe, dentro do runtime.
 * Objetos armazenam uma classe e variáveis de instância.
 */
public class PotigolObject {
  private PotigolClass potigolClass;
  private HashMap<String, PotigolObject> instanceVariables;
  
  /**
   * Cria uma instância de uma classe PotigolClass
   */
  public PotigolObject(PotigolClass potigolClass) {
    this.potigolClass = potigolClass;
    this.instanceVariables = new HashMap<String, PotigolObject>();
  }
  
  public PotigolObject(String className) {
    this(PotigolRuntime.getRootClass(className));
  }
  
  public PotigolObject() {
    this(PotigolRuntime.getObjectClass());
  }
  
  public PotigolClass getPotigolClass() {
    return potigolClass;
  }
  
  public void setPotigolClass(PotigolClass klass) {
    potigolClass = klass;
  }
  
  public PotigolObject getInstanceVariable(String name) {
    if (hasInstanceVariable(name))
      return instanceVariables.get(name);
    return PotigolRuntime.getNil();
  }

  public boolean hasInstanceVariable(String name) {
    return instanceVariables.containsKey(name);
  }
  
  public void setInstanceVariable(String name, PotigolObject value) {
    instanceVariables.put(name, value);
  }
  
  /**
   * Invoca um método do objeto.
   */
  public PotigolObject call(String method, PotigolObject arguments[]) throws PotigolException {
    return potigolClass.lookup(method).call(this, arguments);
  }

  public PotigolObject call(String method) throws PotigolException {
    return call(method, new PotigolObject[0]);
  }
  
  /**
   * Apenas falso e nulo não são verdadeiros.
   */
  public boolean isTrue() {
    return !isFalse();
  }
  
  /**
   * Apenas falso e nulo são falsos. 
   * Este método é sobrescrito em ValueObject.
   */
  public boolean isFalse() {
    return false;
  }

  /**
   * Apenas nulo é nulo.
   * Este método é sobrescrito em ValueObject.
  */
  public boolean isNil() {
    return false;
  }
  
  /**
   * Converte para um objeto Java.
   * Este método é sobrescrito em ValueObject.
   */
  public Object toJavaObject() {
    return this;
  }
  
  public <T> T as(Class<T> clazz) throws TypeError {
    if (clazz.isInstance(this)){
      return clazz.cast(this);
    }
    throw new TypeError(clazz.getName(), this);
  }
  
  public String asString() throws TypeError {
    return as(ValueObject.class).getValueAs(String.class);
  }

  public Integer asInteger() throws TypeError {
    return as(ValueObject.class).getValueAs(Integer.class);
  }

  public Float asFloat() throws TypeError {
    return as(ValueObject.class).getValueAs(Float.class);
  }
}
